// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <memory>

#include "cangjie/Parse/Parser.h"
#include "cangjie/Frontend/CompilerInstance.h"
#include "cangjie/AST/Node.h"

#include "Platform/FileName.h"

#include "CangjieTools/Compiler.h"

// If need to open DebugInfoAttacher can define DEBUG_MIGRATOR

#ifdef DEBUG_MIGRATOR
#include "DebugInfoAttacher.h"
#endif

using namespace CangjieMigrator;
using namespace CangjieMigrator::CangjieTools;
using namespace Platform;

using Cangjie::DiagnosticEngine;
using Cangjie::CompilerInvocation;
using Cangjie::GlobalOptions;
using Cangjie::CompilerInstance;
using Cangjie::AST::ASTKIND_TO_STRING_MAP;
using Cangjie::AST::ImportKind;
using Cangjie::AST::Node;
using Cangjie::AST::VisitAction;

Compiler::Compiler(Path cangjieHome) : cangjieHome(std::move(cangjieHome)), tempDirectory(TempDirectory())
{}

Path Compiler::GetOutputPath() const
{
    return tempDirectory.GetPath();
}

void Compiler::CompileSemaFile(const std::vector<Path> &files, const std::vector<Path> &imports)
{
    CompileSema(files, imports, false);
}

void Compiler::CompileSemaPackage(const UpdateInstance::Package &package)
{
    CompileSemaPackage({package.GetPath()}, package.GetImportPaths());
}

void Compiler::CompileSemaPackage(const std::vector<Path> &packages, const std::vector<Path> &imports)
{
    CompileSema(packages, imports, true);
}

static std::string CollapseBackslashes(const std::string &in)
{
    if (in.empty()) {
        return in;
    }

    std::string out;
    out.reserve(in.size());

    auto startsWith = [&in](const char *pfx) {
        const size_t n = std::strlen(pfx);
        return in.size() >= n && std::memcmp(in.data(), pfx, n) == 0;
    };

    size_t i = 0;
    if (startsWith(R"(\\?\)")) {
        out.append(R"(\\?\)");
        i = 4; // 4 is sizeof(R"(\\?\)")
    } else if (startsWith(R"(\\.\)")) {
        out.append(R"(\\.\)");
        i = 4; // 4 is sizeof(R"(\\.\)")
    } else if (in.rfind(R"(\\)", 0) == 0) {
        out.append(R"(\\)");
        i = 2; // 2 is sizeof(R"(\\)")
    }

    for (; i < in.size(); ++i) {
        char c = in[i];
        if (c == '\\') {
            out.push_back('\\');
            while (i + 1 < in.size() && in[i + 1] == '\\') {
                ++i;
            }
        } else {
            out.push_back(c);
        }
    }
    return out;
}

void Compiler::CompileSema(const std::vector<Path> &inputs, const std::vector<Path> &imports, bool isCompilePackage)
{
    auto diagnosticEngine = DiagnosticEngine();
    diagnosticEngine.SetIsEmitter(false);
    diagnosticEngine.SetDisableWarning(true);
    auto invocation = std::make_unique<CompilerInvocation>();
    invocation->globalOptions.outputMode = GlobalOptions::OutputMode::STATIC_LIB;
    invocation->globalOptions.outputDir = GetOutputPath().string();
    invocation->globalOptions.implicitPrelude = true;
    invocation->globalOptions.chirWFC = true;
    invocation->globalOptions.executablePath = (cangjieHome / "bin" / FileName::Cjc).string();
    invocation->globalOptions.enableMacroInLSP = true;
    if (isCompilePackage) {
        invocation->globalOptions.compilePackage = true;
        std::transform(inputs.begin(), inputs.end(),
                       std::back_inserter(invocation->globalOptions.packagePaths),
                       [](const Path &p) { return CollapseBackslashes(p.string()); });
    } else {
        invocation->globalOptions.compilePackage = false;
        std::transform(inputs.begin(), inputs.end(),
                       std::back_inserter(invocation->globalOptions.srcFiles),
                       [](const Path &p) { return CollapseBackslashes(p.string()); });
    }
    std::transform(imports.begin(), imports.end(),
                   std::back_inserter(invocation->globalOptions.importPaths),
                   [](const Path &p) { return CollapseBackslashes(p.string()); });
    invocation->globalOptions.environment.cangjieHome = CollapseBackslashes(cangjieHome.string());
    instance = std::make_unique<CompilerInstance>(*invocation, diagnosticEngine);
    instance->cangjieHome = CollapseBackslashes(cangjieHome.string());
    const auto libPathName = invocation->globalOptions.GetCangjieLibTargetPathName();
    instance->cangjieModules = CollapseBackslashes((cangjieHome / "modules" / libPathName).string());
    Compile();
}

AstRootNode Compiler::GetAstRootNode() const
{
    return instance->GetSourcePackages().back()->files.back().get();
}

std::vector<AstRootNode> Compiler::GetAstRootNodes() const
{
    std::vector<AstRootNode> astRootNodes;
    for (const auto &file : instance->GetSourcePackages().back()->files) {
        astRootNodes.emplace_back(file);
    }
    return astRootNodes;
}

using Cangjie::AST::Walker;

void Compiler::TraverseAst(const AstRootNode rootNode, const TraverseFunc &func)
{
    Walker(rootNode, nullptr, [func](const Ptr<Node> node) { return func(*node); }).Walk();
}

void Compiler::TraverseAst(const AstRootNode rootNode,
    const std::unique_ptr<Rules::RuleList> &rules, UpdateInstance::File &file)
{
    auto fileId = rootNode->begin.fileID;
#ifdef DEBUG_MIGRATOR
    auto attacher = DebugInfoAttacher(file.GetPath());

    const Node *current = nullptr;
    try {
        TraverseAst(rootNode, [&rules, &file, &attacher, &current, fileId](const Node &node) {
            current = &node;
            const auto range = std::make_unique<PositionRange>(node);
            if (range->start.fileID != fileId || range->end.fileID != fileId) {
                return VisitAction::WALK_CHILDREN;
            }
            if (range->IsInvalid() || range->IsEmpty()) {
                return VisitAction::WALK_CHILDREN;
            }
            attacher.Attach(node, *range);
            rules->Check(file, node, *range);
            return VisitAction::WALK_CHILDREN;
        });
    } catch (const std::exception &e) {
        std::cerr << "Throw exception when traverse AST tree: " << e.what() << std::endl;
        attacher.Attach(current->begin.line, "error");
        throw;
    }
    (void)attacher.Generate();
#else
    TraverseAst(rootNode, [&rules, &file, fileId](const Node &node) {
        const auto range = std::make_unique<PositionRange>(node);
        if (range->start.fileID != fileId || range->end.fileID != fileId) {
            return VisitAction::WALK_CHILDREN;
        }
        if (range->IsInvalid() || range->IsEmpty()) {
            return VisitAction::WALK_CHILDREN;
        }
        rules->Check(file, node, *range);
        return VisitAction::WALK_CHILDREN;
    });
#endif // DEBUG_MIGRATOR
}

void Compiler::Compile() const
{
    instance->PerformParse();
    instance->PerformConditionCompile();
    instance->PerformImportPackage();
    instance->PerformMacroExpand();
    instance->PerformSema();
}
